﻿#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
//VS2022还是有点毛病的非说这个函数不安全让我添加也是逆天了
//我们用Windows的API

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <thread>
#include <atomic>
#include <windows.h>
#include <vector>
#include <locale>
#include <codecvt>

#pragma comment(lib, "ws2_32.lib")//孩子们在Windows使用网络通信要连接这个库

class UdpClient //客户端类，嘻嘻
{
private:
    SOCKET _sockfd;  //文件描述符，所以你打开套接字相当于是开了个文件描述符
    std::string _ip;  //点分十进制的ip
    uint16_t _port;  //端口
    sockaddr_in _server;//存储ip地址
    std::atomic<bool> _running;//判断运行的状态
    HANDLE _consoleHandle;//Windows控制台的句柄
    std::vector<std::wstring> _messageHistory;//历史消息
    int _maxDisplayLines;//定义的最大展示的行数
    std::string _clientIP;//本地的ip
    uint16_t _clientPort;//duan口

    // 获取本地真实IP
    std::string GetLocalIP() 
    {
        SOCKET tempSock = socket(AF_INET, SOCK_DGRAM, 0);
        sockaddr_in googleDNS;
        googleDNS.sin_family = AF_INET;
        googleDNS.sin_port = htons(53);
        inet_pton(AF_INET, "8.8.8.8", &googleDNS.sin_addr);

        connect(tempSock, (sockaddr*)&googleDNS, sizeof(googleDNS));

        sockaddr_in localAddr;
        int addrLen = sizeof(localAddr);
        getsockname(tempSock, (sockaddr*)&localAddr, &addrLen);
        closesocket(tempSock);

        char ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(localAddr.sin_addr), ip, INET_ADDRSTRLEN);
        return ip;
    }

    void InitLocalAddress() 
    {
        _clientIP = GetLocalIP();

        sockaddr_in clientAddr;
        memset(&clientAddr, 0, sizeof(clientAddr));
        clientAddr.sin_family = AF_INET;
        clientAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        clientAddr.sin_port = htons(0);

        if (bind(_sockfd, (sockaddr*)&clientAddr, sizeof(clientAddr)) == SOCKET_ERROR) 
        {
            std::wcerr << L"绑定端口失败: " << WSAGetLastError() << std::endl;
            exit(1);
        }

        int addrLen = sizeof(clientAddr);
        getsockname(_sockfd, (sockaddr*)&clientAddr, &addrLen);
        _clientPort = ntohs(clientAddr.sin_port);
    }

    std::wstring Utf8ToWide(const std::string& utf8) 
    {
        if (utf8.empty()) return L"";
        int size_needed = MultiByteToWideChar(CP_UTF8, 0, &utf8[0], (int)utf8.size(), NULL, 0);
        std::wstring wstr(size_needed, 0);
        MultiByteToWideChar(CP_UTF8, 0, &utf8[0], (int)utf8.size(), &wstr[0], size_needed);
        return wstr;
    }

    std::string WideToUtf8(const std::wstring& wstr) 
    {
        if (wstr.empty()) return "";
        int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
        std::string str(size_needed, 0);
        WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &str[0], size_needed, NULL, NULL);
        return str;
    }

    void SetupConsole() 
    {
        _consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleOutputCP(CP_UTF8);
        SetConsoleCP(CP_UTF8);

        CONSOLE_FONT_INFOEX fontInfo;
        fontInfo.cbSize = sizeof(fontInfo);
        GetCurrentConsoleFontEx(_consoleHandle, FALSE, &fontInfo);
        wcscpy_s(fontInfo.FaceName, L"新宋体");
        SetCurrentConsoleFontEx(_consoleHandle, FALSE, &fontInfo);

        SetConsoleTitle(L"UDP聊天室");
    }

    void PrintInterface() 
    {
        system("cls");

        SetConsoleTextAttribute(_consoleHandle, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
        std::wcout << L"============ UDP聊天室 ============\n";
        SetConsoleTextAttribute(_consoleHandle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

        int start = (_messageHistory.size() > _maxDisplayLines) ?
            (_messageHistory.size() - _maxDisplayLines) : 0;

        for (size_t i = start; i < _messageHistory.size(); i++) 
        {
            std::wcout << _messageHistory[i] << L"\n";
        }

        std::wcout << L"----------------------------------\n";
        std::wcout << L"请输入消息(输入quit退出): ";
        std::wcout.flush();
    }

    void AddMessage(const std::wstring& msg, bool isSelf = false) 
    {
        _messageHistory.push_back((isSelf ? L"我(" + Utf8ToWide(_clientIP) + L":" + std::to_wstring(_clientPort) + L"): "
            : L"") + msg);
        if (_messageHistory.size() > _maxDisplayLines * 2) {
            _messageHistory.erase(_messageHistory.begin());
        }
        PrintInterface();
    }

    std::wstring ParseMessage(const std::string& rawMsg)
    {
        size_t pos = rawMsg.find('#');
        if (pos != std::string::npos && pos + 1 < rawMsg.size()) 
        {
            // 提取发送者IP和消息内容
            std::string senderInfo = rawMsg.substr(0, pos);
            std::string content = rawMsg.substr(pos + 1);

            // 返回格式化消息 "IP:端口> 消息内容"
            return Utf8ToWide(senderInfo) + L"> " + Utf8ToWide(content);
        }
        return Utf8ToWide(rawMsg);
    }

    void Recev() //回调函数真好玩ლ(´ڡ`ლ)
    {
        char buffer[1024];
        while (_running) 
        {
            sockaddr_in temp;
            int len = sizeof(temp);
            int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0,
                (sockaddr*)&temp, &len);

            if (n > 0) 
            {
                buffer[n] = 0;
                std::wstring wmsg = ParseMessage(buffer);
                AddMessage(wmsg);
            }
        }
    }

public:
    UdpClient(const std::string& ip, const uint16_t port, int maxDisplayLines = 15)//构造函数，爽
        : _sockfd(INVALID_SOCKET), _ip(ip), _port(port),
        _running(false), _maxDisplayLines(maxDisplayLines) 
    {
        SetupConsole();
    }

    ~UdpClient() 
    {
        if (_sockfd != INVALID_SOCKET) 
        {
            closesocket(_sockfd);
        }
        WSACleanup();
    }

    void InitClient() //要初始化一下参数
    {
        WSADATA wsaData;
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) 
        {
            std::wcerr << L"WSAStartup失败: " << WSAGetLastError() << std::endl;
            exit(1);
        }

        _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (_sockfd == INVALID_SOCKET) 
        {
            std::wcerr << L"创建套接字失败: " << WSAGetLastError() << std::endl;
            exit(1);
        }

        InitLocalAddress();

        memset(&_server, 0, sizeof(_server));
        _server.sin_family = AF_INET;
        _server.sin_port = htons(_port);
        if (inet_pton(AF_INET, _ip.c_str(), &_server.sin_addr) <= 0) 
        {
            std::wcerr << L"无效的IP地址" << std::endl;
            exit(1);
        }
    }

    void Start() //启动函数嘤嘤嘤
    {
        _running = true;
        PrintInterface();

        std::thread t(&UdpClient::Recev, this);
        t.detach();

        // 发送上线通知
        std::wstring online = L"加入了聊天室";
        std::string utf8Online = WideToUtf8(online);
        std::string fullMsg = _clientIP + ":" + std::to_string(_clientPort) + "#" + utf8Online;
        sendto(_sockfd, fullMsg.c_str(), fullMsg.size(), 0,
            (sockaddr*)&_server, sizeof(_server));
        AddMessage(online, true);

        while (_running) 
        {
            std::wstring message;
            std::getline(std::wcin, message);

            if (message.empty()) continue;

            if (message == L"quit") 
            {
                _running = false;
                std::wstring quit = L"离开了聊天室";
                std::string utf8Quit = WideToUtf8(quit);
                std::string fullQuitMsg = _clientIP + ":" + std::to_string(_clientPort) + "#" + utf8Quit;
                sendto(_sockfd, fullQuitMsg.c_str(), fullQuitMsg.size(), 0,
                    (sockaddr*)&_server, sizeof(_server));
                AddMessage(quit, true);
                break;
            }

            std::string utf8Msg = WideToUtf8(message);
            std::string fullMsg = _clientIP + ":" + std::to_string(_clientPort) + "#" + utf8Msg;
            if (sendto(_sockfd, fullMsg.c_str(), fullMsg.size(), 0,
                (sockaddr*)&_server, sizeof(_server)) == SOCKET_ERROR) 
            {
                std::wcerr << L"发送失败: " << WSAGetLastError() << std::endl;
            }
            AddMessage(message, true);
        }
    }
};

int main() 
{
    std::locale::global(std::locale("zh_CN.UTF-8"));//兼容linux与Windows的字符，要不然输入中文就是乱码
    //设置宽字符输入输出流（wcout 和 wcin）的本地化（locale）环境
    std::wcout.imbue(std::locale());
    std::wcin.imbue(std::locale());

    try //尝试抛一个异常
    {
        UdpClient client("82.157.70.111", 8080);//原本应该用命令行参数让你知道进那个服务器，但是为了适配2022我直接给默认了
        //你要是不改下面的ip，端口，就只会进我的那个服务器
        client.InitClient();//初始化
        client.Start();//启动
    }
    catch (const std::exception& e) //该异常类型是exception
    {
        std::wcerr << L"发生异常: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}